/**
 * 
 */
package org.dse.proc;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dse.util.Constants;

/**
 * This class wraps the RandomAccessFile and its memory-mapping function. Give
 * the outside to read a large file fast and in a convenient way. Because the
 * raw files may be very large, we need to access it in an efficient way. The
 * memory-mapping function is proper.
 * 
 * The class provides some useful methods similar to common readers in java.io
 * package, like readerLine(), read().
 * 
 * @author zhangchen
 * 
 */
public class RawHtmlFilesReader {

	private RandomAccessFile rawFile;

	private FileChannel channel;

	private MappedByteBuffer buffer;

	private static final Log log = LogFactory.getLog(RawHtmlFilesReader.class);

	/**
	 * The constructor. Open the file in read mode and map it to memory.
	 * 
	 * @param fileName
	 *            the file to be read
	 */
	public RawHtmlFilesReader(String fileName) {
		try {
			rawFile = new RandomAccessFile(fileName, "r");// only for read

			channel = rawFile.getChannel();
			long size = channel.size();

			// Use memory-mapping file to improve the efficiency.
			buffer = channel.map(MapMode.READ_ONLY, 0, size);
		} catch (IOException e) {
			e.printStackTrace();
			log.error("Can not open the raw file.", e);
		}
	}

	/**
	 * Set the start position for next read.
	 * 
	 * @param pos
	 *            the position to read
	 */
	public void setOffset(int pos) {
		buffer.position(pos);
	}

	/**
	 * Get the position of the read operation.
	 * 
	 * @return the current position
	 */
	public int getOffset() {
		return buffer.position();
	}

	/**
	 * Similar to readLine() in BufferedReader class. Return null if end of
	 * buffer.
	 * 
	 * @return a string in one line, end by \n, \r or \r\n
	 */
	public String readLine() {
		StringBuffer input = new StringBuffer();
		boolean eol = false;

		while (!eol) {
			if (!buffer.hasRemaining() && input.length() == 0)// end of file
				return null;
			else if (!buffer.hasRemaining())
				return input.toString();

			char nextChar = readChar();
			if (nextChar == Constants.LINE_SEPERATOR)// \n(unix)
				eol = true;
			else if (nextChar == '\r') {// /r/n(windows)
				eol = true;
				int cur = buffer.position();
				if (readChar() != Constants.LINE_SEPERATOR) {
					buffer.position(cur);
				}
			} else
				input.append(nextChar);

		}
		return input.toString();
	}

	/**
	 * Read a char, using 2 bytes to form a char
	 * 
	 * @return an unicode char containing 2 bytes
	 */
	public char readChar() {
		if (!buffer.hasRemaining())
			return (char) -1;

		byte byte1 = buffer.get();
		byte byte2 = buffer.get();

		String temp = null;
		try {
			temp = new String(new byte[] { byte1, byte2 },
					Constants.DEFAULT_CHARSET);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			System.exit(1);
		}
		if (temp.length() == 0)
			return readChar();
		return temp.charAt(0);
	}

	/**
	 * Read more than one char into an array.
	 * 
	 * @param buf
	 *            the array which holds the chars
	 * @return the number of chars read actually
	 */
	public int read(char[] buf) {
		// int count = 0;
		if (!buffer.hasRemaining())
			return -1;

		int remain = buffer.remaining();
		int length = buf.length * 2;
		byte[] byteArray = new byte[remain < length ? remain : length];

		buffer.get(byteArray);

		/*
		 * while (buffer.hasRemaining() && count < buf.length) {
		 * buf[count++] = readChar(); }
		 */

		String temp = null;
		try {
			temp = new String(byteArray, Constants.DEFAULT_CHARSET);
			int s_length = temp.length();
			for (int i = 0; i < s_length; i++) {
				buf[i] = temp.charAt(i);
			}

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			System.exit(1);
		}

		return temp.length();
	}

	/**
	 * Close the reader to release the resource.
	 */
	public void close() {
		try {
			channel.close();
			rawFile.close();
		} catch (IOException e) {
			e.printStackTrace();
			log.error("Close exception.", e);
		}
	}
}
